{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Welcome to Week 6 Day 3!\n",
    "\n",
    "Let's experiment with a bunch more MCP Servers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from agents import Agent, Runner, trace\n",
    "from agents.mcp import MCPServerStdio\n",
    "import os\n",
    "from IPython.display import Markdown, display\n",
    "from datetime import datetime\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The first type of MCP Server: runs locally, everything local\n",
    "\n",
    "Here's a really interesting one: a knowledge-graph based memory.\n",
    "\n",
    "It's a persistent memory store of entities, observations about them, and relationships between them.\n",
    "\n",
    "https://github.com/modelcontextprotocol/servers/tree/main/src/memory\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = {\"command\": \"npx\",\"args\": [\"-y\", \"mcp-memory-libsql\"],\"env\": {\"LIBSQL_URL\": \"file:./memory/ed.db\"}}\n",
    "\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
    "    mcp_tools = await server.list_tools()\n",
    "\n",
    "mcp_tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions = \"You use your entity tools as a persistent memory to store and recall information about your conversations.\"\n",
    "request = \"My name's Ed. I'm an LLM engineer. I'm teaching a course about AI Agents, including the incredible MCP protocol. \\\n",
    "MCP is a protocol for connecting agents with tools, resources and prompt templates, and makes it easy to integrate AI agents with capabilities.\"\n",
    "model = \"gpt-4.1-mini\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
    "    agent = Agent(name=\"agent\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
    "    with trace(\"conversation\"):\n",
    "        result = await Runner.run(agent, request)\n",
    "    display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
    "    agent = Agent(name=\"agent\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
    "    with trace(\"conversation\"):\n",
    "        result = await Runner.run(agent, \"My name's Ed. What do you know about me?\")\n",
    "    display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Check the trace:\n",
    "\n",
    "https://platform.openai.com/traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The 2nd type of MCP server - runs locally, calls a web service\n",
    "\n",
    "### Brave Search - apologies - this will need another API key! But it's free again.\n",
    "\n",
    "https://brave.com/search/api/\n",
    "\n",
    "Set up your account, and put your key in the .env under `BRAVE_API_KEY`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "env = {\"BRAVE_API_KEY\": os.getenv(\"BRAVE_API_KEY\")}\n",
    "params = {\"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-brave-search\"], \"env\": env}\n",
    "\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
    "    mcp_tools = await server.list_tools()\n",
    "\n",
    "mcp_tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions = \"You are able to search the web for information and briefly summarize the takeaways.\"\n",
    "request = f\"Please research the latest news on Amazon stock price and briefly summarize its outlook. \\\n",
    "For context, the current date is {datetime.now().strftime('%Y-%m-%d')}\"\n",
    "model = \"gpt-4o-mini\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
    "    agent = Agent(name=\"agent\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
    "    with trace(\"conversation\"):\n",
    "        result = await Runner.run(agent, request)\n",
    "    display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### As usual, check out the trace:\n",
    "\n",
    "https://platform.openai.com/traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## And now the third type: running remotely\n",
    "\n",
    "It's actually really hard to find a \"remote MCP server\" aka \"hosted MCP server\" aka \"managed MCP server\".\n",
    "\n",
    "It's not a common model for using or sharing MCP servers, and there isn't a standard way to discover remote MCP servers.\n",
    "\n",
    "Anthropic lists some remote MCP servers, but these are for paid applications with business users:\n",
    "\n",
    "https://docs.anthropic.com/en/docs/agents-and-tools/remote-mcp-servers\n",
    "\n",
    "CloudFlare has tooling for you to create and deploy your own remote MCP servers, but this does not seem to be a common practice:\n",
    "\n",
    "https://developers.cloudflare.com/agents/guides/remote-mcp-server/\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# And back to the 2nd type: the Polygon.io MCP Server"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left; width:100%\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/stop.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#ff7800;\">PLEASE READ!!-</h2>\n",
    "            <span style=\"color:#ff7800;\">This service for financial market data has both a FREE plan and a PAID plan, and we can use either depending on your appetite.\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## NEW SECTION: Introducing polygon.io\n",
    "\n",
    "Polygon.io is a hugely popular financial data provider. It has a free plan and a paid plan. And it also has an MCP Server!\n",
    "\n",
    "First, read up on polygon.io on their excellent website, including looking at their pricing:\n",
    "\n",
    "https://polygon.io\n",
    "\n",
    "### Polygon.io Part 1: Polygon.io free service (the paid will be totally optional, of course!)\n",
    "\n",
    "1. Please sign up for polygon.io (top right)  \n",
    "2. Once signed in, please select \"Keys\" in the left hand navigation\n",
    "3. Press the blue \"New Key\" button\n",
    "4. Copy the key name\n",
    "5. Edit your .env file and add the row:\n",
    "\n",
    "`POLYGON_API_KEY=xxxx`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv(override=True)\n",
    "polygon_api_key = os.getenv(\"POLYGON_API_KEY\")\n",
    "if not polygon_api_key:\n",
    "    print(\"POLYGON_API_KEY is not set\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from polygon import RESTClient\n",
    "client = RESTClient(polygon_api_key)\n",
    "client.get_previous_close_agg(\"AAPL\")[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Wrapped into a python module that caches end of day prices\n",
    "\n",
    "I've made a python module `market.py` that uses this API to look up share prices.\n",
    "\n",
    "But the free API is quite heavily rate limited - so I've been a bit sneaky; when you ask for a share price, this function retrieves the entire end-of-day equity market, and caches it in our database.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from market import get_share_price\n",
    "get_share_price(\"AAPL\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# no rate limiting concerns!\n",
    "\n",
    "for i in range(1000):\n",
    "    get_share_price(\"AAPL\")\n",
    "get_share_price(\"AAPL\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### And I've made this into an MCP Server\n",
    "\n",
    "Just as we did with accounts.py; see `market_server.py`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = {\"command\": \"uv\", \"args\": [\"run\", \"market_server.py\"]}\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=60) as server:\n",
    "    mcp_tools = await server.list_tools()\n",
    "mcp_tools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Let's try it out!\n",
    "\n",
    "Hopefully gpt-4o-mini is smart enough to know that the symbol for Apple is AAPL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions = \"You answer questions about the stock market.\"\n",
    "request = \"What's the share price of Apple?\"\n",
    "model = \"gpt-4.1-mini\"\n",
    "\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=60) as mcp_server:\n",
    "    agent = Agent(name=\"agent\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
    "    with trace(\"conversation\"):\n",
    "        result = await Runner.run(agent, request)\n",
    "    display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Polygon.io Part 2: Paid Plan - Totally Optional!\n",
    "\n",
    "If you are interested, you can subscribe to the monthly plan to get more up to date market data, and unlimited API calls.\n",
    "\n",
    "If you do wish to do this, then it also makes sense to use the full MCP server that Polygon.io has released, to take advantage of all their functionality.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "params = {\"command\": \"uvx\",\n",
    "          \"args\": [\"--from\", \"git+https://github.com/polygon-io/mcp_polygon@v0.1.0\", \"mcp_polygon\"],\n",
    "          \"env\": {\"POLYGON_API_KEY\": polygon_api_key}\n",
    "          }\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=60) as server:\n",
    "    mcp_tools = await server.list_tools()\n",
    "mcp_tools\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Wow that's a lot of tools!\n",
    "\n",
    "Let's try them out - hopefully the sheer number of tools doesn't overwhelm gpt-4o-mini!\n",
    "\n",
    "With the $29 monthly plan, we don't have access to some of the APIs, so I've needed to specify which APIs can be called.\n",
    "\n",
    "If you've splashed out on a bigger plan, feel free to remove my extra constraint.."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions = \"You answer questions about the stock market.\"\n",
    "request = \"What's the share price of Apple? Use your get_snapshot_ticker tool to get the latest price.\"\n",
    "model = \"gpt-4.1-mini\"\n",
    "\n",
    "async with MCPServerStdio(params=params, client_session_timeout_seconds=60) as mcp_server:\n",
    "    agent = Agent(name=\"agent\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
    "    with trace(\"conversation\"):\n",
    "        result = await Runner.run(agent, request)\n",
    "    display(Markdown(result.final_output))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting up your .env file\n",
    "\n",
    "If you do decide to have a paid plan, please add this to your .env file to indicate:\n",
    "\n",
    "`POLYGON_PLAN=paid`\n",
    "\n",
    "And if you decide to go all the way for the realtime API, then please do:\n",
    "\n",
    "`POLYGON_PLAN=realtime`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv(override=True)\n",
    "\n",
    "polygon_plan = os.getenv(\"POLYGON_PLAN\")\n",
    "is_paid_polygon = polygon_plan == \"paid\"\n",
    "is_realtime_polygon = polygon_plan == \"realtime\"\n",
    "\n",
    "if is_paid_polygon:\n",
    "    print(\"You've chosen to subscribe to the paid Polygon plan, so the code will look at prices on a 15 min delay\")\n",
    "elif is_realtime_polygon:\n",
    "    print(\"Wowzer - you've chosen to subscribe to the realtime Polygon plan, so the code will look at realtime prices\")\n",
    "else:\n",
    "    print(\"According to your .env file, you've chosen to subscribe to the free Polygon plan, so the code will look at EOD prices\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## And that's it for today!\n",
    "\n",
    "I've removed the part of this lab that uses the \"Financial Datasets\" mcp server, because it's inferior - more expensive with fewer APIs.\n",
    "\n",
    "And this way we get to use the same provider for Free and Paid APIs.\n",
    "\n",
    "But if you want to see the code, just look in the git history for a prior version."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left; width:100%\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/exercise.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#ff7800;\">Exercises</h2>\n",
    "            <span style=\"color:#ff7800;\">Explore MCP server marketplaces and integrate your own, using all 3 approaches.\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
